package task

import (
	"bytes"
	"log"
	"src/config"
	connection2 "src/connection"
	global2 "src/global"
	"src/utils"

	//"github.com/prometheus/common/log"
	"os"
	"os/signal"
	"src/model/ProtoModel"
	"strconv"
	"strings"
	"syscall"
	"time"
)

type updateOnLineTimeFangUserDbm struct {
	sql      string
	uids     []string
	pingTime int64
	roomNo   string
}
type Purification struct {
	//#任务执行状态
	RepeatRunning                bool
	UpdateOnLineTimeFangUserDbms []updateOnLineTimeFangUserDbm
}

// 倒计时
func (this *Purification) Countdown(duration time.Duration, callback func()) {
	timer := time.NewTimer(duration)

	go func() {
		<-timer.C
		callback()
	}()
}
func (this *Purification) callback() {

}

// 服务关闭逻辑处理，用于kill进程的时候拦截，结束所有未完成任务后在关闭
func (this *Purification) Serverdown() {
	// 服务关闭逻辑处理
	// 创建一个通道来接收系统kill 关闭信号
	sigs := make(chan os.Signal, 1)
	// 通过signal.Notify函数把我们想要监听的信号添加进去
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	// 这个goroutine会等待任一信号的到来
	go func() {
		sig := <-sigs
		// 在这里可以添加任何清理代码，比如关闭文件、释放资源等
		// 确保程序能够优雅地退出
		var callback func()
		duration := 1 * time.Second
		// 倒计时结束时要执行的操作
		callback = func() {
			config.V().Set("server-close.close", true)
			stopTime := config.V().GetInt64("server-close.stop-remaining-time")
			if stopTime > 0 {
				log.Println("关闭倒计时！" + strconv.FormatInt(stopTime, 10) + "秒")
				stopTime = stopTime - 1
				config.V().Set("server-close.stop-remaining-time", stopTime)
				// 倒计时秒
				this.Countdown(duration, callback)
				return
			} else {
				if len(this.UpdateOnLineTimeFangUserDbms) > 0 {
					stopTime = stopTime + 10
					log.Println("有房间在线时长更新任务未完成，关闭倒计时-延时" + strconv.FormatInt(stopTime, 10) + "秒")
					config.V().Set("server-close.stop-remaining-time", stopTime)
					// 倒计时秒
					this.Countdown(duration, callback)
					return
				}
			}
			log.Println("倒计时结束：服务器正常关闭！")
			os.Exit(0)
		}
		startTime := config.V().GetInt64("server-close.start-time")
		this.Countdown(time.Duration(startTime)*time.Second, callback)
		log.Println("收到关闭命令:服务进程会在" + strconv.FormatInt(startTime, 10) + "秒后停止接入新的链接请求")
		log.Println(sig)
	}()
}

//var updateOnLineTimeFangUserDbms *[]*updateOnLineTimeFangUserDbm

// 心跳检测   每秒遍历一次 查看所有sess 上次接收消息时间  如果超过 num 就删除该 sess
func (this *Purification) HeartBeat() {
	breakTimeout := config.V().GetInt64("heartbeat.break-timeout")
	msgFmt := config.V().GetString("client.msg-fmt")
	tcpOpen := config.V().GetBool("server-tcp-isOpen")
	websocketOpen := config.V().GetBool("server.websocket.isOpen")
	if tcpOpen {
		go this.HeartBeatTcp(breakTimeout, msgFmt)
	}
	//go this.heartBeatWs(breakTimeout, msgFmt)
	if websocketOpen {
		go this.HeartBeatWs(breakTimeout, msgFmt)
	}
}
func (this *Purification) HeartBeatTcp(breakTimeout int64, msgFmt string) {
	//msgFmt := config.V().GetString("client.msg-fmt")
	conlist := global2.Tcp_.ConnList
	if conlist != nil {
		for _, conn := range conlist {
			if conn != nil {
				if conn.Times == 0 {
					conn.UpdateTime()
				}
				//如果上次接收消息超过num秒 就删除该用户信息

				tfc := time.Now().Unix() - conn.Times
				if conn.IsClosed || tfc > breakTimeout {
					// unsafe.Pointer 是内存指针 转换方法
					this.CleanConStateWs(conn.Id)
					break
				}
				this.PingTcp(conn, msgFmt)
			}

		}
	}
}
func (this *Purification) GetUpdateOnLineTimeFangUserDbms() []updateOnLineTimeFangUserDbm {
	if this.UpdateOnLineTimeFangUserDbms == nil {
		this.UpdateOnLineTimeFangUserDbms = []updateOnLineTimeFangUserDbm{}
	}
	return this.UpdateOnLineTimeFangUserDbms
}
func removeElement(s []int, index int) []int {
	return append(s[:index], s[index+1:]...)
}

// 重复执行 对失败的任务 重复处理 这个是防止数据库关闭时候，数据更新失败，可保证下次数据库链接成功的时候数据会更新进去
func (this *Purification) RepeatRun() {
	if this.RepeatRunning {
		return
	}
	this.RepeatRunning = true
	isUpdateOnLineTimeFangUser := config.V().GetBool("heartbeat.is-update-on-line-time-fang-user")
	if isUpdateOnLineTimeFangUser {
		//取出所有数据
		userDbms := this.UpdateOnLineTimeFangUserDbms
		// 数据置空
		//this.UpdateOnLineTimeFangUserDbms = []updateOnLineTimeFangUserDbm{}
		if userDbms != nil && len(userDbms) > 0 {
			//	失败
			log.Println("RepeatRun sql, uids, pingTime, roomNo len", len(userDbms))
			db := utils.NewDbClient("db", nil)
			if db != nil {
				for _, _ = range userDbms {
					userDbm := this.UpdateOnLineTimeFangUserDbms[0]
					this.UpdateOnLineTimeFangUserDbms = this.UpdateOnLineTimeFangUserDbms[1:]
					this.updateOnLineTimeFangUser(userDbm.sql, userDbm.uids, userDbm.pingTime, userDbm.roomNo, db)
					log.Println("RepeatRun---------------------------")
				}
				db.Client.Close()
			}
		}
	}

	this.RepeatRunning = false
}

// 更新数据库 房间用户 在线时长字段
func (this *Purification) updateOnLineTimeFangUser(sqlstr string, uids []string, pingTime int64, roomNo string, db *utils.DbClient) bool {

	sql := sqlstr + "(" + strings.Join(uids, ",") + ")"

	if db == nil {
		db = utils.NewDbClient("db", nil)
	}
	_, err := db.Client.Exec(sql, pingTime, roomNo)
	if err != nil {
		//	失败
		log.Println("更新房间用户在线时长失败:"+sql, uids, pingTime, roomNo)
		// 缓存到计划任务中
		this.UpdateOnLineTimeFangUserDbms = append(this.GetUpdateOnLineTimeFangUserDbms(), updateOnLineTimeFangUserDbm{sqlstr, uids, pingTime, roomNo})
		return false
	}
	return true
}

// 房间心跳
func (this *Purification) HeartBeatFangWs(pingTime int64) {
	roomList := global2.Ws_.GeRoomList()
	MyGlobal := global2.Ws_
	updateOnLineTimeFangUser := config.V().GetBool("heartbeat.is-update-on-line-time-fang-user")
	sql := config.V().GetString("heartbeat.update-on-line-time-fang-user-sql")
	db := utils.NewDbClient("db", nil)
	for roomNo, cons := range roomList {
		uids := []string{}
		for connId, uid := range cons {
			conn := MyGlobal.GetConnListVal(connId)
			//判断是否在这个房间
			if conn.RoomNo == roomNo {
				uids = append(uids, uid)
			} else {
				//从房间里清除
				global2.Ws_.DelRoomList(roomNo, connId)
			}
		}
		if updateOnLineTimeFangUser && len(uids) > 0 {
			this.updateOnLineTimeFangUser(sql, uids, pingTime, roomNo, db)
		}
		//	//从房间里清除
		//if roomList[roomNo] != nil && MyGlobal.RoomList[conn.RoomNo][connID] != "" {
		//	delete(MyGlobal.RoomList[conn.RoomNo], connID)
		//}

	}
}

// 房间心跳
func (this *Purification) HeartBeatFangTcp(pingTime int64) {
	roomList := global2.Tcp_.GeRoomList()
	MyGlobal := global2.Tcp_
	//pingTime := viper.GetInt64("heartbeat.ping-time")
	updateOnLineTimeFangUser := config.V().GetBool("heartbeat.is-update-on-line-time-fang-user")
	sql := config.V().GetString("heartbeat.update-on-line-time-fang-user-sql")
	db := utils.NewDbClient("db", nil)
	for roomNo, cons := range roomList {
		uids := []string{}
		for connId, uid := range cons {
			conn := MyGlobal.GetConnListVal(connId)
			//判断是否在这个房间
			if conn.RoomNo == roomNo {
				uids = append(uids, uid)
			} else {
				////从房间里清除
				//delete(global.Tcp_.RoomList[roomNo], connId)
			}
		}
		if updateOnLineTimeFangUser && len(uids) > 0 {
			this.updateOnLineTimeFangUser(sql, uids, pingTime, roomNo, db)
		}

	}
	db.Client.Close()
}
func (this *Purification) HeartBeatWs(breakTimeout int64, msgFmt string) {
	conlist := global2.Ws_.GetConnList()
	if conlist != nil {
		for _, conn := range conlist {
			if conn != nil {
				if conn.Times == 0 {
					conn.UpdateTime()
				}
				//如果上次接收消息超过num秒 就删除该用户信息

				tfc := time.Now().Unix() - conn.Times
				if conn.IsClosed || tfc > breakTimeout {
					// unsafe.Pointer 是内存指针 转换方法
					this.CleanConStateWs(conn.Id)
					break
				}
				this.PingWs(conn, msgFmt)
			}

		}
	}
}

// 关闭并删除这个链接
func (*Purification) CleanConStateWs(connID string) {
	MyGlobal := global2.Ws_
	conn := MyGlobal.GetConnListVal(connID)
	// 关闭链接
	defer func() {
		conn.Close()
	}()
	if conn.Uid != "" {
		userInfos := MyGlobal.GetUserListVal(conn.Uid)
		if userInfos != nil && len(userInfos) > 0 {
			var userInfos2 []string
			for _, ConID := range userInfos {
				if conn.Id != ConID {
					userInfos2 = append(userInfos2, ConID)
				}
			}
			if len(userInfos2) > 0 {
				MyGlobal.UpdateUserList(conn.Uid, userInfos2)
			} else {
				MyGlobal.DelUserList(conn.Uid, "")
				//delete(MyGlobal.UserList, conn.Uid)
			}
		}
	}
	if MyGlobal.ConnList[connID] != nil {
		roomList := MyGlobal.GetRoomListVal(conn.RoomNo)
		//从房间里清除
		if roomList != nil && roomList[connID] != "" {
			MyGlobal.DelRoomList(conn.RoomNo, connID)
		}
		//删除这个链接缓存
		MyGlobal.DelConnList(connID)
	}
	return
}
func (*Purification) CleanConStateTcp(connID string) {
	MyGlobal := global2.Tcp_
	conn := MyGlobal.GetConnListVal(connID)
	// 关闭链接
	defer func() {
		conn.Close()
	}()

	if conn.Uid != "" {
		userInfos := MyGlobal.GetUserListVal(conn.Uid)
		if userInfos != nil && len(userInfos) > 0 {
			var userInfos2 []string
			for _, ConID := range userInfos {
				if conn.Id != ConID {
					userInfos2 = append(userInfos2, ConID)
				}
			}
			if len(userInfos2) > 0 {
				MyGlobal.UpdateUserList(conn.Uid, userInfos2)
			} else {
				MyGlobal.DelUserList(conn.Uid, "")
			}
		}
	}
	if MyGlobal.ConnList[connID] != nil {
		roomList := MyGlobal.GetRoomListVal(conn.RoomNo)
		//从房间里清除
		if roomList != nil && roomList[connID] != "" {

			MyGlobal.DelRoomList(conn.RoomNo, connID)
		}
		//删除这个链接缓存
		MyGlobal.DelConnList(connID)
	}
	return
}

// 这是向客户端发送心跳
func (this *Purification) PingTcp(conn *connection2.Tcp, msgFmt string) {
	if conn != nil {
		if msgFmt == "json" {
			toMsg := bytes.NewBuffer([]byte{uint8(ProtoModel.DataTypeProto_PING.Number())})
			//replyBody := `{"Key":"` + datatype.String() + `,"Data":"` + string(data) + `"}`
			toMsg.Write([]byte(ProtoModel.DataTypeProto_PING.String()))
			err := conn.WriteMes(toMsg.Bytes())
			if err != nil {
				conn.Close()
				return
			}
		} else {
			toMsg := bytes.NewBuffer([]byte{uint8(ProtoModel.DataTypeProto_PING.Number())})
			//toMsg.Write([]byte{uint8(ProtoModel.DataTypeProto_PING.Number())})
			toMsg.Write([]byte(ProtoModel.DataTypeProto_PING.String()))
			//pkg := utils.PkgBuf.PkgData(utils.PkgBuf{}, toMsg.Bytes(), 1) //测试包装数据 4->protobuf data struct len
			err := conn.WriteMes(toMsg.Bytes())
			if err != nil {
				conn.Close()
				return
			}
		}
	}

}

// 这是向客户端发送心跳
func (this *Purification) PingWs(conn *connection2.Ws, msgFmt string) {
	if conn != nil {
		if msgFmt == "json" {
			toMsg := bytes.NewBuffer([]byte{uint8(ProtoModel.DataTypeProto_PING.Number())})
			//replyBody := `{"Key":"` + datatype.String() + `,"Data":"` + string(data) + `"}`
			toMsg.Write([]byte(ProtoModel.DataTypeProto_PING.String()))
			err := conn.WriteMes(toMsg.Bytes())
			if err != nil {
				conn.Close()
				return
			}

		} else {
			toMsg := bytes.NewBuffer([]byte{uint8(ProtoModel.DataTypeProto_PING.Number())})
			//toMsg.Write([]byte{uint8(ProtoModel.DataTypeProto_PING.Number())})
			toMsg.Write([]byte(ProtoModel.DataTypeProto_PING.String()))
			//pkg := utils.PkgBuf.PkgData(utils.PkgBuf{}, toMsg.Bytes(), 1) //测试包装数据 4->protobuf data struct len
			err := conn.WriteMes(toMsg.Bytes())
			if err != nil {
				conn.Close()
				return
			}
		}
	}

}
